use crate::config::CONTEXT;
use crate::global::{fname_to_nv, DpmCell, DpmError, RespVO};
use crate::subcmd::basecmd::BaseCmd;
use crate::subcmd::{chk_pkginstall_path, is_ymlconf, lazy_init_ymlconf};
use async_recursion::async_recursion;
use async_trait::async_trait;
use rcmd_core::clap::ArgMatches;
use rcmd_core::log::*;
use rcmd_core::log::{error, info};
use rcmd_core::queue::Queue;
use rcmd_core::util::filesys::unzip_to_path;
use std::env;
use std::env::temp_dir;
use std::path::Path;

use super::basecmd;

///模块安装工具
///自动会检测是否有依赖项全部安装到本地
type DwInfo = (String, String);

struct Install {
    ///执行安装命令的路径
    path: String,
    ///要安装的插件的完整名字  ex: pn@1.2.0
    fpn: String,
}

struct DwMgr {
    ///所有要下载的具体文件及对应文件名
    dw_list: Queue<DwInfo>,
    ///所有要下载的pm列表
    pm_list: Queue<String>,
}

impl DwMgr {
    pub fn default() -> Self {
        Self {
            dw_list: Queue::new(),
            pm_list: Queue::new(),
        }
    }

    pub async fn download(&mut self) -> Vec<Option<String>> {
        let cnt = self.dw_list.len();
        let mut dw_l = vec![];
        for _ in 0..cnt {
            let f = self.oss_download().await;
            dw_l.push(f);
        }
        return dw_l;
    }

    pub async fn oss_download(&mut self) -> Option<String> {
        let dw_cell = self.dw_list.pop().unwrap();
        debug!("OSS: {} => {}", dw_cell.0.as_str(), dw_cell.1.as_str());
        let f =
            crate::oss::download(&CONTEXT.oss_config, dw_cell.0.as_str(), dw_cell.1.as_str()).await;
        if let Some(_fr) = f {
            debug!("download suc!");
            return Some(dw_cell.1);
        }
        debug!("download failed!");
        return None;
    }

    pub async fn url_download(&mut self) -> Option<String> {
        //TODO: download by http
        return None;
    }
}

impl Install {
    pub fn default(path: &str, fpn: &str) -> Self {
        Self {
            path: path.to_string(),
            fpn: fpn.to_string(),
        }
    }

    pub async fn api_remote_info<'a>(name: &'a str, ver: &'a str) -> surf::Result<RespVO<DpmCell>> {
        let ver_parm = if ver.len() > 0 { ver } else { "+" };
        let api_url = format!(
            "{uri_base}/api/dpm/info?name={name}&ver={ver}",
            uri_base = CONTEXT.uri_base,
            name = name,
            ver = ver_parm
        );

        debug!("api req->\n{}", api_url);
        info!("{}", surf::get(api_url.to_string()).recv_string().await?);
        let ret = surf::get(api_url).recv_json::<RespVO<DpmCell>>().await;
        return ret;
    }

    #[async_recursion]
    pub async fn install_one_pm(pm: &str, dw: &mut DwMgr) -> Result<(), DpmError> {
        info!("installing <{pm}>...", pm = pm);
        let temp = temp_dir();
        //chk info
        dw.pm_list.push(pm.to_string());
        let (name, ver) = fname_to_nv(pm);
        let ret: Result<RespVO<DpmCell>, surf::Error> = Install::api_remote_info(name, ver).await;
        let dep_l: Vec<String> = vec![];
        match ret {
            Ok(v) => {
                if v.code != 0 {
                    return Err(DpmError::ApiFailed(v.msg));
                }
                let cur = env::current_dir().unwrap();
                let tf = Path::new(cur.as_os_str())
                    .join(temp)
                    .join(format!("{pm}.zip", pm = name))
                    .to_str()
                    .unwrap()
                    .to_string();
                dw.dw_list.push((
                    format!(
                        "{path}/{name}.zip",
                        path = CONTEXT.oss_config.path.as_deref().unwrap_or_default(),
                        name = pm
                    ),
                    tf,
                ));
            }
            Err(_) => {
                return Err(DpmError::ApiFailed(
                    "api get remote info failed!".to_string(),
                ));
            }
        };
        //检查依赖
        if dep_l.len() > 0 {
            for d_pm in dep_l {
                dw.pm_list.push(d_pm.to_string());
                let ret = Install::install_one_pm(d_pm.as_str(), dw).await;
                if ret.is_err() {
                    error!("install pm = {} failed!", d_pm);
                    std::process::exit(2);
                }
            }
        }
        Ok(())
    }
}

#[async_trait]
impl BaseCmd for Install {
    async fn run(&self) -> Result<(), DpmError> {
        //env
        let ins_p = chk_pkginstall_path(&self.path);
        if is_ymlconf(&self.path) == false {
            info!("current path not init yet! lazy init first...");
            _ = lazy_init_ymlconf(&self.path);
            info!("suc!")
        }
        //download
        let mut dw = DwMgr::default();
        let ins_ret = Install::install_one_pm(&self.fpn, &mut dw).await;
        if ins_ret.is_err() {
            return ins_ret;
        }
        self.execute_hook(
            basecmd::HookSupport::BeforeInstall,
            &vec![
                // $1
                env::current_dir().ok().unwrap().display().to_string(),
                // $2
                ins_p.display().to_string(),
                // $3
                self.fpn.to_string(),
            ],
        );
        //todo pbr下载进度打印
        info!("start download...");
        let dw_l = dw.download().await;

        //unzip
        for f in dw_l {
            let c = match f {
                Some(v) => v,
                None => {
                    return Err(DpmError::DownloadFiled);
                }
            };
            let d: &std::ffi::OsStr = Path::new(c.as_str()).file_stem().unwrap();
            //todo pbr跟进解压进度
            _ = unzip_to_path(c.as_str(), Path::new(&ins_p).join(d).to_str().unwrap());
        }
        info!("install suc!");
        self.execute_hook(
            basecmd::HookSupport::AfterInstall,
            &vec![
                // $1
                env::current_dir().ok().unwrap().display().to_string(),
                // $2
                ins_p.display().to_string(),
                // $3
                self.fpn.to_string(),
            ],
        );
        Ok(())
    }
}

pub async fn handle(subm: &ArgMatches) {
    let cp = env::current_dir().unwrap();
    let fpn = subm.get_one::<String>("plugin").unwrap();
    let cmd = Install::default(cp.to_str().unwrap(), fpn);
    cmd.run().await.unwrap_or_else(|e| {
        error!("{}", e.to_string().as_str());
    });
}

#[test]
fn test_install() {
    use std::env::temp_dir;
    let path_tmp = temp_dir();
    //chk init
    let path_str = path_tmp.display().to_string();
    let cmd = Install::default(&path_str.to_string(), "dingcode-editor-win32");
    let _ = cmd.run();
}
